package com.gambi.example.tapestry.services;

import java.io.IOException;

import org.apache.tapestry5.Block;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.StreamResponse;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.Invocation;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.MethodAdvice;
import org.apache.tapestry5.ioc.MethodAdviceReceiver;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Match;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.runtime.RenderCommand;
import org.apache.tapestry5.services.HttpServletRequestFilter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestGlobals;
import org.apache.tapestry5.urlrewriter.RewriteRuleApplicability;
import org.apache.tapestry5.urlrewriter.SimpleRequestWrapper;
import org.apache.tapestry5.urlrewriter.URLRewriteContext;
import org.apache.tapestry5.urlrewriter.URLRewriterRule;

import com.gambi.example.client.chatter.model.Contact;
import com.gambi.example.client.chatter.model.Message;
import com.gambi.example.client.chatter.services.MessengerService;
import com.gambi.example.tapestry.services.chatter.MessengerServiceImpl;

/**
 * This module is automatically included as part of the Tapestry IoC Registry,
 * it's a good place to configure and extend Tapestry, or to place your own
 * service definitions.
 */
public class AppModule {
	public static void bind(ServiceBinder binder) {
		binder.bind(GWTServletHttpRequestFilter.class);
		binder.bind(GWTComponentEventResultProcessor.class);
		// Chatter service
		binder.bind(MessengerService.class, MessengerServiceImpl.class);
	}

	public static void contributeApplicationDefaults(
			MappedConfiguration<String, String> configuration) {
		configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en");
		String environment = System.getProperty("environment");
		if (environment == null)
			environment = "prod";

		if (environment.equals("dev")) {
			configuration.add(SymbolConstants.PRODUCTION_MODE, "false");
		}
		if (environment.equals("prod")) {
			configuration.add(SymbolConstants.PRODUCTION_MODE, "false");
		}
		configuration.add(SymbolConstants.APPLICATION_VERSION, "1.0-SNAPSHOT");
	}

	// Remember to launch your app with the -DcodeServer=127.0.0.1:9997
	static final String codeServer = System.getProperty("codeServer");

	/*
	 * This will add the missing part when we run the application as HOSTED
	 * mode. TODO Maybe this is something useful to bind to the
	 * Tapestry.ProductionMode symbol
	 */
	public static void contributeURLRewriter(
			OrderedConfiguration<URLRewriterRule> configuration) {

		if (codeServer != null) {
			URLRewriterRule rule = new URLRewriterRule() {

				public RewriteRuleApplicability applicability() {
					return RewriteRuleApplicability.OUTBOUND;
				}

				public Request process(Request request,
						URLRewriteContext context) {
					// if this is a component event, getPageParameters() will
					// // return null.
					if (context.getPageParameters() == null)
						return request;
					// not really right but good enough for dev
					String path = request.getPath() + "?gwt.codesvr="
							+ codeServer;
					return new SimpleRequestWrapper(request, path);
				}
			};
			configuration.add("rule1", rule);
		}
	}

	public void contributeHttpServletRequestHandler(
			OrderedConfiguration<HttpServletRequestFilter> configuration,
			GWTServletHttpRequestFilter filter) {

		configuration.add("GWT", filter, "after:*");
	}

	@Match("*ComponentEventResultProcessor")
	public static void adviseGwtRequest(
			MethodAdviceReceiver receiver,
			final RequestGlobals requestGlobals,
			final GWTComponentEventResultProcessor gWTComponentEventResultProcessor) {

		MethodAdvice advice = new MethodAdvice() {
			public void advise(Invocation invocation) {
				// Check for the requestContent
				System.out.println("- " + invocation.getMethodName());
				System.out.println("- " + invocation.getParameterCount());
				System.out.println("- " + invocation.getParameter(0));
				System.out.println("- " + invocation.getResult());
				System.out.println("- " + invocation.getResultType());

				if (requestGlobals.getHTTPServletRequest().getContentType()
						.contains("text/x-gwt-rpc")) {

					/*
					 * Here is still missing a way to identify truly String
					 * return type and return types that come from the Tapestry
					 * way of doing things: Link -> Page class -> Page name
					 * (string) -> JSONObject.
					 * 
					 * What is also missing is an idea on how to deal with
					 * redirect contentType. At the moment I simply encode the
					 * link assuming that the GWT component knows how to deal
					 * with that.
					 */
					if (invocation.getParameter(0) instanceof Block) {
						// Default: convert to String the rendered block ?
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof RenderCommand) {
						// Default: convert to String ?
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof Link) {
						// Default: convert to URL-String ?
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof JSONObject) {
						// Default: convert to GWT JSONObject or String
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof JSONArray) {
						// Default: this will be intercepted by the Filter that
						// converts to GWT JSONArray or String
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof StreamResponse) {
						// Default: convert to GWT JSONArray
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof Class) {
						// Default: convert to URL-String ?
						invocation.proceed();
					} else if (invocation.getParameter(0) instanceof String) {

						// TODO Here find a way to catch the case of
						// self-invocation when one use page names, links, Page
						// classes and so on.
						// Default: convert to URL-String ?
						invocation.proceed();
					} else {
						try {
							// This must be the invocation to the
							gWTComponentEventResultProcessor
									.processResultValue(invocation
											.getParameter(0));
							// invocation.overrideResult(null);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
							throw new RuntimeException(e);
						}
					}

				} else {
					// System.out
					// .println("AppModule.adviseGwtRequest(...).new MethodAdvice() {...}.advise(): NOT A GWT RPC");
					// This is the original invocation
					invocation.proceed();
				}
			}
		};
		receiver.adviseAllMethods(advice);
	};

	public static void contributeTypeCoercer(
			Configuration<CoercionTuple> configuration) {

		configuration.add(new CoercionTuple<String, Contact>(String.class,
				Contact.class, new Coercion<String, Contact>() {

					public Contact coerce(String input) {
						return new Contact(input);
					}

				}));

		configuration.add(new CoercionTuple<Contact, String>(Contact.class,
				String.class, new Coercion<Contact, String>() {

					public String coerce(Contact input) {
						return input.getName();
					}

				}));

		configuration.add(new CoercionTuple<Message, String>(Message.class,
				String.class, new Coercion<Message, String>() {

					public String coerce(Message input) {
						return input.toString();
					}

				}));

		configuration.add(new CoercionTuple<String, Message>(String.class,
				Message.class, new Coercion<String, Message>() {

					public Message coerce(String input) {
						return new Message(input);
					}

				}));
	}
}
